"""

Template for Objects

Copy this module up one level and name it as you like, then
use it as a template to create your own Objects.

To make the default commands default to creating objects of your new
type (and also change the "fallback" object used when typeclass
creation fails), change settings.BASE_OBJECT_TYPECLASS to point to
your new class, e.g.

settings.BASE_OBJECT_TYPECLASS = "game.gamesrc.objects.myobj.MyObj"

Note that objects already created in the database will not notice
this change, you have to convert them manually e.g. with the
@typeclass command.

"""
from ev import Object
from src.utils.utils import variable_from_module
from ev import utils, settings
from src.objects.models import ObjectDB
from game.gamesrc.utils import myutils

_AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit('.', 1))

_ME = "me"
_SELF = "self"
_HERE = "here"

class MyObject(Object):
    """
    This is the root typeclass object, implementing an in-game Evennia
    game object, such as having a location, being able to be
    manipulated or looked at, etc. If you create a new typeclass, it
    must always inherit from this object (or any of the other objects
    in this file, since they all actually inherit from BaseObject, as
    seen in src.object.objects).

    The BaseObject class implements several hooks tying into the game
    engine. By re-implementing these hooks you can control the
    system. You should never need to re-implement special Python
    methods, such as __init__ and especially never __getattribute__ and
    __setattr__ since these are used heavily by the typeclass system
    of Evennia and messing with them might well break things for you.


    * Base properties defined/available on all Objects

     key (string) - name of object
     name (string)- same as key
     aliases (list of strings) - aliases to the object. Will be saved to database as AliasDB entries but returned as strings.
     dbref (int, read-only) - unique #id-number. Also "id" can be used.
     dbobj (Object, read-only) - link to database model. dbobj.typeclass points back to this class
     typeclass (Object, read-only) - this links back to this class as an identified only. Use self.swap_typeclass() to switch.
     date_created (string) - time stamp of object creation
     permissions (list of strings) - list of permission strings

     player (Player) - controlling player (will also return offline player)
     location (Object) - current location. Is None if this is a room
     home (Object) - safety start-location
     sessions (list of Sessions, read-only) - returns all sessions connected to this object
     has_player (bool, read-only)- will only return *connected* players
     contents (list of Objects, read-only) - returns all objects inside this object (including exits)
     exits (list of Objects, read-only) - returns all exits from this object, if any
     destination (Object) - only set if this object is an exit.
     is_superuser (bool, read-only) - True/False if this user is a superuser

    * Handlers available

     locks - lock-handler: use locks.add() to add new lock strings
     db - attribute-handler: store/retrieve database attributes on this self.db.myattr=val, val=self.db.myattr
     ndb - non-persistent attribute handler: same as db but does not create a database entry when storing data
     scripts - script-handler. Add new scripts to object with scripts.add()
     cmdset - cmdset-handler. Use cmdset.add() to add new cmdsets to object
     nicks - nick-handler. New nicks with nicks.add().

    * Helper methods (see src.objects.objects.py for full headers)

     search(ostring, global_search=False, attribute_name=None, use_nicks=False, location=None, ignore_errors=False, player=False)
     execute_cmd(raw_string)
     msg(message, from_obj=None, data=None)
     msg_contents(message, exclude=None, from_obj=None, data=None)
     move_to(destination, quiet=False, emit_to_obj=None, use_destination=True)
     copy(new_key=None)
     delete()
     is_typeclass(typeclass, exact=False)
     swap_typeclass(new_typeclass, clean_attributes=False, no_default=True)
     access(accessing_obj, access_type='read', default=False)
     check_permstring(permstring)

    * Hooks (these are class methods, so their arguments should also start with self):

     basetype_setup()     - only called once, used for behind-the-scenes setup. Normally not modified.
     basetype_posthook_setup() - customization in basetype, after the object has been created; Normally not modified.

     at_object_creation() - only called once, when object is first created. Object customizations go here.
     at_object_delete() - called just before deleting an object. If returning False, deletion is aborted. Note that all objects
                          inside a deleted object are automatically moved to their <home>, they don't need to be removed here.

     at_init()            - called whenever typeclass is cached from memory, at least once every server restart/reload
     at_cmdset_get()      - this is called just before the command handler requests a cmdset from this object
     at_first_login()     - (player-controlled objects only) called once, the very first time user logs in.
     at_pre_login()       - (player-controlled objects only) called every time the user connects, after they have identified, before other setup
     at_post_login()      - (player-controlled objects only) called at the end of login, just before setting the player loose in the world.
     at_disconnect()      - (player-controlled objects only) called just before the user disconnects (or goes linkless)
     at_server_reload()   - called before server is reloaded
     at_server_shutdown() - called just before server is fully shut down

     at_access_success(accessing_obj, access_type) - called if an lock access check succeeded on this object
     at_access_failure(accessing_obj, access_type) - called if an lock access check failed on this object

     at_before_move(destination)             - called just before moving object to the destination. If returns False, move is cancelled.
     announce_move_from(destination)         - called in old location, just before move, if obj.move_to() has quiet=False
     announce_move_to(source_location)       - called in new location, just after move, if obj.move_to() has quiet=False
     at_after_move(source_location)          - always called after a move has been successfully performed.
     at_object_leave(obj, target_location)   - called when an object leaves this object in any fashion
     at_object_receive(obj, source_location) - called when this object receives another object

     at_before_traverse(traversing_object)                 - (exit-objects only) called just before an object traverses this object
     at_after_traverse(traversing_object, source_location) - (exit-objects only) called just after a traversal has happened.
     at_failed_traverse(traversing_object)      - (exit-objects only) called if traversal fails and property err_traverse is not defined.

     at_msg_receive(self, msg, from_obj=None, data=None) - called when a message (via self.msg()) is sent to this obj.
                                                           If returns false, aborts send.
     at_msg_send(self, msg, to_obj=None, data=None) - called when this objects sends a message to someone via self.msg().

     return_appearance(looker) - describes this object. Used by "look" command by default
     at_desc(looker=None)      - called by 'look' whenever the appearance is requested.
     at_get(getter)            - called after object has been picked up. Does not stop pickup.
     at_drop(dropper)          - called when this object has been dropped.
     at_say(speaker, message)  - by default, called if an object inside this object speaks

     """
    def at_object_creation(self):
        super(MyObject, self).at_object_creation()
        self.locks.add("view:can_see()")
        self.locks.add("call:compare_attr(wild_loc)")
        self.db.wild_loc = (None,None)
        self.db.desc = "Non noti niente di speciale."
        self.db.flags = []

    def return_appearance(self, pobject):
        """
        This is a convenient hook for a 'look'
        command to call.
        """
        if not pobject:
            return
        string = "{c%s{n" % self.name.capitalize()
        desc = utils.fill(self.attr("desc"))
        if desc:
            string += "\n %s" % desc
        return string

    def wild_search(self, ostring,
               attribute_name=None,
               use_nicks=False,
               location=None,
               ignore_errors=False,
               player=False):
        """
        Search things by wild_loc attribute. There is no global_search
        here, it could lead to unexpected behaviours like returning
        objects with same wild_loc from other wilderness rooms.
        """

        # handle some common self-references:
        if ostring == _HERE:
            return self.location
        if ostring in (_ME, _SELF, '*' + _ME, '*' + _SELF):
            return self

        if use_nicks:
            if ostring.startswith('*') or player:
                # player nick replace
                ostring = self.nicks.get(ostring.lstrip('*'), nick_type="player")
                if not player:
                    ostring = "*%s" % ostring
            else:
                # object nick replace
                ostring = self.nicks.get(ostring, nick_type="object")

        candidates = []
        if not location:
            location = [self, self.location]
        [candidates.extend(element.contents) for element in utils.make_iter(location)]
        def f(x):
            if (hasattr(x, "wild_loc") 
            and x.wild_loc == self.wild_loc) or x.location == self or (hasattr(x.location, "location")
                                                                   and x.location.wild_loc == self.wild_loc):
                return x.dbobj

        candidates = map(f, candidates)
        candidates = filter(lambda x: x, candidates)
        results = ObjectDB.objects.object_search(ostring, caller=None,
                                             attribute_name=attribute_name,
                                             candidates=candidates,
                                             exact=False)
        if ignore_errors:
            return results
        result = _AT_SEARCH_RESULT(self, ostring, results, False)
        return result

    def search(self, ostring,
               global_search=False,
               attribute_name=None,
               use_nicks=False,
               location=None,
               ignore_errors=False,
               player=False):
        """
        Perform a standard object search in the database, handling
        multiple results and lack thereof gracefully.

        ostring: (str) The string to match object names against.
                       Obs - To find a player, append * to the
                       start of ostring.
        global_search(bool): Search all objects, not just the current
                       location/inventory
        attribute_name (string) Which attribute to match
                        (if None, uses default 'name')
        use_nicks (bool) : Use nickname replace (off by default)
        location (Object): If None, use caller's current location
        ignore_errors (bool): Don't display any error messages even
                        if there are none/multiple matches -
                        just return the result as a list.
        player (Objectt):  Don't search for an Object but a Player.
                           This will also find players that don't
                           currently have a character.

        Returns - a unique Object/Player match or None. All error
                  messages are handled by system-commands and the parser-handlers
                  specified in settings.

        Use *<string> to search for objects controlled by a specific
        player. Note that the object controlled by the player will be
        returned, not the player object itself. This also means that
        this will not find Players without a character. Use the keyword
        player=True to find player objects.

        Note - for multiple matches, the engine accepts a number
        linked to the key in order to separate the matches from
        each other without showing the dbref explicitly. Default
        syntax for this is 'N-searchword'. So for example, if there
        are three objects in the room all named 'ball', you could
        address the individual ball as '1-ball', '2-ball', '3-ball'
        etc.
        """
        # check whether we are in a wilderness
        if 'WILD' in self.location.db.flags and not global_search:
            return self.wild_search(ostring,
                                 attribute_name=attribute_name,
                                 use_nicks=use_nicks,
                                 location=location,
                                 ignore_errors=ignore_errors,
                                 player=player)

        return self.dbobj.search(ostring,
                                 global_search=global_search,
                                 attribute_name=attribute_name,
                                 use_nicks=use_nicks,
                                 location=location,
                                 ignore_errors=ignore_errors,
                                 player=player)


    def wild_move_to(self, destination, wild_loc):
        """
        Moves this object to a new location in the wilderness. Destination is the 
        wilderness (as a room), wild_loc must be a tuple. Note that no lock checks 
        are done by this function, such things are assumed to have been handled 
        before calling move_to.

        destination: (tuple) Reference to the object to move to. This
                     can also be an exit object, in which case the destination
                     property is used as destination.
        quiet:  (bool)    If true, don't emit left/arrived messages.
        emit_to_obj: (Object) object to receive error messages
        use_destination (bool): Default is for objects to use the "destination" property
                              of destinations as the target to move to. Turning off this
                              keyword allows objects to move "inside" exit objects.
        Returns True/False depending on if there were problems with the move. This method
                may also return various error messages to the emit_to_obj.

        """
        if not (0 <= wild_loc[0] < destination.db.x_lenght) or not (0 <= wild_loc[1] < destination.db.y_lenght):
            return False
        if not wild_loc or wild_loc == (None, None): 
            return False
        if not destination.access(self, 'traverse', wild_loc):
            return False
        self.db.wild_loc = wild_loc
        return True


    def move_to(self, destination, quiet=False,
                emit_to_obj=None, use_destination=True, wild_loc=None):
        """
        Moves this object to a new location. Note that if <destination> is an
        exit object (i.e. it has "destination"!=None), the move_to will
        happen to this destination and -not- into the exit object itself, unless
        use_destination=False. Note that no lock checks are done by this function,
        such things are assumed to have been handled before calling move_to.

        destination: (Object) Reference to the object to move to. This
                     can also be an exit object, in which case the destination
                     property is used as destination.
        quiet:  (bool)    If true, don't emit left/arrived messages.
        emit_to_obj: (Object) object to receive error messages
        use_destination (bool): Default is for objects to use the "destination" property
                              of destinations as the target to move to. Turning off this
                              keyword allows objects to move "inside" exit objects.
        Returns True/False depending on if there were problems with the move. This method
                may also return various error messages to the emit_to_obj.

        """
        if wild_loc and 'WILD' in destination.db.flags:
            self.wild_move_to(destination, wild_loc)
            if self.location == destination: return True #it means that the object change just wild_loc, but not location
        return self.dbobj.move_to(destination, quiet=quiet,
                                  emit_to_obj=emit_to_obj, use_destination=use_destination)
            

    def msg(self, message, obj_list=None, from_obj=None, data=None, blind=True):
        """
        Emits something to any sessions attached to the object.

        message (str): The message to send
        from_obj (obj): object that is sending.
        data (object): an optional data object that may or may not
                       be used by the protocol.
        """
        if not obj_list :
            self.dbobj.msg(message, from_obj=from_obj, data=data)
            return
        something = "qualcosa"
        somebody = "qualcuno"
        def f(x):
            if x.access(self, 'view'): return x.name
            if x.has_player or 'MOB' in x.db.flags: return somebody
            else: return something
        names = map(f, obj_list)
        message = message % tuple(names)
        self.dbobj.msg(myutils.capital(message), from_obj=from_obj, data=data)

    def msg_contents(self, message, exclude=None, from_obj=None, data=None, obj_list=None, wild_loc=None):
        """
        Emits something to all objects inside an object.

        exclude is a list of objects not to send to. See self.msg() for more info.

        """
        if exclude:
            exclude = utils.make_iter(exclude)
            contents = [obj for obj in self.contents if obj not in exclude]
        else: 
            contents = self.contents
        for obj in contents:
            obj.msg(message, obj_list=obj_list, from_obj=from_obj, data=data)
        """
        self.dbobj.msg_contents(message,  exclude=exclude, from_obj=from_obj, data=data)

        contents = _GA(self, "contents")
        if exclude:
            exclude = make_iter(exclude)
            contents = [obj for obj in contents
                        if (obj not in exclude and obj not in exclude)]

        """
